package com.bluemoon.bmwalk.common.jacoco;

import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.BRANCH_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.CLASS;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.CLASS_NAME;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.CLASS_SOURCEFILENAME;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.CLASS_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.COMPLEXITY_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.COUNTER;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.COUNTER_COVERED;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.COUNTER_MISSED;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.COUNTER_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.INSTRUCTION_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_CB;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_CI;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_MB;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_MI;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_NR;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.LINE_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.METHOD;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.METHOD_DESC;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.METHOD_NAME;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.METHOD_TYPE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.PACKAGE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.PACKAGE_NAME;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.REPORT;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.REPORT_NAME;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.SESSIONINFO;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.SOURCEFILE;
import static com.bluemoon.bmwalk.common.jacoco.JacocoXmlConstant.SOURCEFILE_NAME;

import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import com.bluemoon.bmwalk.common.jacoco.dto.Clazz;
import com.bluemoon.bmwalk.common.jacoco.dto.Counter;
import com.bluemoon.bmwalk.common.jacoco.dto.Coverage;
import com.bluemoon.bmwalk.common.jacoco.dto.Line;
import com.bluemoon.bmwalk.common.jacoco.dto.Method;
import com.bluemoon.bmwalk.common.jacoco.dto.Package;
import com.bluemoon.bmwalk.common.jacoco.dto.Report;
import com.bluemoon.bmwalk.common.jacoco.dto.Sourcefile;

import lombok.Getter;

/**
 * 解析xml文件
 * @author: liaozhioyng
 * @create: 2024-01-03
 */
@Getter
public class ParseXmlHandler extends DefaultHandler {

    private Report report;

    private Package currentPackage;

    private Clazz currentClass;

    private Method currentMethod;

    private Counter currentCounter;

    private Sourcefile sourcefile;
    
    private Line line;

    /**
     * 开始解析元素时触发
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        switch (qName) {
            // 解析report
            case REPORT:
                report = new Report();
                report.setName(attributes.getValue(REPORT_NAME));
                break;
            // 跳过 sessioninfo 和 line
            case SESSIONINFO:
                return;
            // 解析package
            case PACKAGE:
                currentPackage = new Package();
                currentPackage.setName(attributes.getValue(PACKAGE_NAME));
                if (report != null) {
                    report.addPackage(currentPackage);
                }
                break;
            // 解析class
            case CLASS:
                currentClass = new Clazz();
                currentClass.setName(attributes.getValue(CLASS_NAME));
                currentClass.setSourcefilename(attributes.getValue(CLASS_SOURCEFILENAME));
                if (currentPackage != null) {
                    currentPackage.addClass(currentClass);
                }
                break;
            // 解析method
            case METHOD:
                currentMethod = new Method();
                currentMethod.setName(attributes.getValue(METHOD_NAME));
                currentMethod.setDesc(attributes.getValue(METHOD_DESC));
                if (currentClass != null) {
                    currentClass.addMethod(currentMethod);
                }
                break;
            // 解析sourcefile
            case SOURCEFILE:
                sourcefile = new Sourcefile();
                sourcefile.setName(attributes.getValue(SOURCEFILE_NAME));
                if (currentPackage != null) {
                    currentPackage.addSourcefile(sourcefile);
                }
                break;
            // 解析counter
            case COUNTER:
                currentCounter = new Counter();
                currentCounter.setType(attributes.getValue(COUNTER_TYPE));
                currentCounter.setMissed(Integer.parseInt(attributes.getValue(COUNTER_MISSED)));
                currentCounter.setCovered(Integer.parseInt(attributes.getValue(COUNTER_COVERED)));
                // 绑定counter 到对应的元素
                bindCounter();
                break;
            case LINE:
            	line = new Line();
                line.setNr(Integer.parseInt(attributes.getValue(LINE_NR)));
                line.setMi(Integer.parseInt(attributes.getValue(LINE_MI)));
                line.setCi(Integer.parseInt(attributes.getValue(LINE_CI)));
                line.setMb(Integer.parseInt(attributes.getValue(LINE_MB)));
                line.setCb(Integer.parseInt(attributes.getValue(LINE_CB)));
                if (sourcefile != null) {
                	sourcefile.addLines(line);
                }
                break;
            default:
                break;
        }

    }


    /**
     * 结束解析元素时触发
     */
    @Override
    public void endElement(String uri, String localName, String qName) {
        switch (qName) {
            case SESSIONINFO:
                // 跳过 sessioninfo 和 line
                return;
            case LINE:
            	line = null;
            	break;
            case SOURCEFILE:
                // 避免重复加入 counter 到 page
                sourcefile = null;
                break;
            case REPORT:
                // 计算总的覆盖率
                Coverage coverageReport = caculateCoverage(report.getReportCounters());
                report.setReportCoverage(coverageReport);
                break;
            case PACKAGE:
                // 计算 package 覆盖率
                if (currentPackage != null) {
                    Coverage coveragePackage = caculateCoverage(currentPackage.getPackageCounters());
                    currentPackage.setPackageCoverage(coveragePackage);
                    currentPackage = null;
                }
                break;
            case CLASS:
                // 计算 class 覆盖率
                if (currentClass != null) {
                    Coverage coverageClass = caculateCoverage(currentClass.getClazzCounters());
                    currentClass.setClazzCoverage(coverageClass);
                    currentClass = null;
                }
                break;
            case METHOD:
                // 计算 method 覆盖率
                if (currentMethod != null) {
                    Coverage coverageMethod = caculateCoverage(currentMethod.getMethodCounters());
                    currentMethod.setMethodCoverage(coverageMethod);
                    currentMethod = null;
                }
                break;
            case COUNTER:
                currentCounter = null;
                break;
            default:
                break;
        }
    }


    /**
     * 绑定counter 到对应的元素
     */
    private void bindCounter() {
        // counter 属于 method
        if (currentMethod != null) {
            currentMethod.addCounter(currentCounter);
        }
        // counter 属于 class
        else if (currentClass != null) {
            currentClass.addCounter(currentCounter);
        }
        // counter 属于 sourcefile
        else if (sourcefile != null) {
        	sourcefile.addCounter(currentCounter);
        }
        // counter 属于 package
        else if (currentPackage != null) {
            // 跳过sourcefile里的counter 避免重复加入
            if (sourcefile == null) {
                currentPackage.addCounter(currentCounter);
            }
        }
        // counter 属于 report
        else if (report != null) {
            report.addCounter(currentCounter);
        }
    }

    /**
     * 计算覆盖率
     */
    private Coverage caculateCoverage(List<Counter> counterList) {
        Coverage result = new Coverage();
        for (Counter counter : counterList) {
            String type = counter.getType().toUpperCase();
            String coverage = processResult(counter);
            setCoverageByType(result, type, coverage);
        }
        return result;
    }

    private void setCoverageByType(Coverage result, String type, String coverage) {
        switch (type) {
            // 指令覆盖率
            case INSTRUCTION_TYPE:
                result.setInstructionCoverage(coverage);
                break;
            // 行覆盖率
            case LINE_TYPE:
                result.setLineCoverage(coverage);
                break;
            // 分支覆盖率
            case BRANCH_TYPE:
                result.setBranchCoverage(coverage);
                break;
            // 圈复杂度覆盖率
            case COMPLEXITY_TYPE:
                result.setComplexityCoverage(coverage);
                break;
            // 方法覆盖率
            case METHOD_TYPE:
                result.setMethodCoverage(coverage);
                break;
            // 类覆盖率
            case CLASS_TYPE:
                result.setClassCoverage(coverage);
                break;
            default:
                break;
        }
    }

    /**
     * 处理覆盖率结果
     * 保留两位小数 例如 99.99%
     */
    private String processResult(Counter counter) {
        int missed = counter.getMissed();
        int covered = counter.getCovered();
        double coverage = (double) covered / (missed + covered);
        double coveragePercentage = coverage * 100;
        return String.format("%.2f%%", coveragePercentage);
    }

}

